﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Waterfood.FolderSizeViewer
{
    public class UI
    {
        private const int ItemsPerPage = 7;
        private int _offset;
        private string _currentPath;
        private List<FolderItem> _currentView;
        private readonly FolderData _data;
        private readonly DataCollector _worker;
        
        public static void StartNew(string root)
        {
            var data = FolderData.Create(root);
            var worker = DataCollector.Create(data);
            var ui = new UI(data, worker);
            ui.Start();
        }

        private UI(FolderData data, DataCollector worker)
        {
            _data = data;
            _worker = worker;
            _offset = 0;
            _currentPath = _data.Root;
            _currentView = new List<FolderItem>();
        }

        private void Start()
        {
            // loop
            bool exit = false;
            bool workerDone = false;
            bool first = true;
            var t1 = ReadUserInputAsync();
            var t2 = _worker.DoWork();
            while (!exit)
            {
                if (workerDone)
                {
                    t1.Wait();
                    exit = t1.Result;
                    t1 = ReadUserInputAsync();
                }
                else
                {
                    var i = Task.WaitAny(t1, t2);
                    if (i == 0)
                    {
                        if (t1.Result)
                        {
                            exit = true;
                        }
                        else
                        {
                            t1 = ReadUserInputAsync();
                        }
                    }
                    else
                    {
                        if (first)
                        {
                            Render();
                            first = false;
                        }
                        if (t2.Result)
                        {
                            workerDone = true;
                        }
                        else
                        {
                            t2 = _worker.DoWork();
                        }
                    }
                }
            }

            // finish
            _data.Save();
            _worker.Save();
        }

        // return true if user presses 'q'
        private async Task<bool> ReadUserInputAsync()
        {
            var key = await ReadKey();
            switch (key.Key)
            {
                case ConsoleKey.Q:
                    return true;
                case ConsoleKey.Backspace:
                case ConsoleKey.LeftArrow:
                    GoBack();
                    break;
                case ConsoleKey.UpArrow:
                case ConsoleKey.PageUp:
                    GoUp();
                    break;
                case ConsoleKey.DownArrow:
                case ConsoleKey.PageDown:
                    GoDown();
                    break;
                default:
                    if (Char.IsDigit(key.KeyChar))
                    {
                        GoDeeper(int.Parse(key.KeyChar.ToString()));
                    }
                    break;
            }
            Render();
            return false;
        }

        private async Task<ConsoleKeyInfo> ReadKey()
        {
            return await Task.Factory.StartNew(() => Console.ReadKey());
        }

        private void GoDeeper(int number)
        {
            int absIndex = number + _offset;
            if (absIndex < _currentView.Count && _currentView[absIndex].IsFolder)
            {
                _currentPath = Path.Combine(_currentPath, _currentView[absIndex].Name);
            }
        }

        private void GoBack()
        {
            if (_currentPath != _data.Root)
            {
                _currentPath = Path.GetDirectoryName(_currentPath);
                _offset = 0;
            }
        }

        private void GoUp()
        {
            if (_offset != 0)
            {
                _offset = Math.Max(_offset - ItemsPerPage, 0);
            }
        }

        private void GoDown()
        {
            if (_offset < _currentView.Count - 1)
            {
                _offset = Math.Min(_offset + ItemsPerPage, _currentView.Count - 1);
            }
        }

        private void Render()
        {
            int files;
            long size;
            _data.GetFolderInfo(_currentPath, out files, out size, out _currentView);
            long totalSize = _data.RootSize();

            int visibleCount = Math.Min(ItemsPerPage, _currentView.Count - _offset);

            double max = _currentView.Max(i => i.Size) * 100.0 / size;
            List<double> percents = _currentView.Skip(_offset).Take(visibleCount).Select(i => i.Size * 100.0 / size).ToList();
            List<double> relativePercents = percents.Select(n => n * 100 / max).ToList();
            List<string> names = _currentView.Skip(_offset).Take(visibleCount).Select(i => i.Name).ToList();
            List<bool> folders = _currentView.Skip(_offset).Take(visibleCount).Select(i => i.IsFolder).ToList();
            Print(_currentPath, size, totalSize, files, names, percents, relativePercents, folders);
        }

        private void Print(string path, long folderSize, long totalSize, int folderFiles, List<string> names, List<double> percents, List<double> relativePercents, List<bool> folders)
        {            
            int NumberFieldWidth = 5;
            int IndexFieldWidth = 5;
            int RightMargin = 4;
            int Gap = 2;
            int nameFieldWidth = -1;
            int barFieldWidth = -1;

            Console.Clear();
            Console.WriteLine("Size calculated: {0}", totalSize.ToHumanReadable());
            Console.WriteLine(path);
            Console.WriteLine("{0}, containing {1} files", folderSize.ToHumanReadable(), folderFiles);
            Console.WriteLine();

            nameFieldWidth = Console.BufferWidth - IndexFieldWidth - RightMargin;
            barFieldWidth = Console.BufferWidth - NumberFieldWidth - Gap - RightMargin;

            if (!(names.Any() && percents.Any()))
            {
                return;
            }

            for (int i = 0; i < names.Count; i++)
            {
                string name = names[i];
                int abs = (int)percents[i];
                double rel = relativePercents[i];
                if (name.Length > nameFieldWidth)
                {
                    name = name.Substring(0, nameFieldWidth - 3) + "...";
                }
                string line1 = string.Format("[{0}]", i).PadRight(IndexFieldWidth);
                int barLength = (int)(rel * barFieldWidth / 100.0);
                string bar = string.Empty.PadRight(barLength, 'o').PadRight(barFieldWidth);
                string line2 = string.Format("{0}%", abs).PadRight(NumberFieldWidth) + bar;
                Console.Write(line1);
                if (folders[i])
                {
                    var oldColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine(name);
                    Console.ForegroundColor = oldColor;
                }
                else
                {
                    Console.WriteLine(name);
                }
                Console.WriteLine(line2);
                Console.WriteLine();
            }
        }
    }
}
